/**
 * 实现动态矩阵类模板
 *
 * 动机：
 * - 教材有矩阵类的例子，元素类型是int。数值计算通常用浮点，有float, double,
 * long double等多种类型，适宜写成类模板。
 * - 如果程序运行时才能确定矩阵行数、列数，就需要动态分配存储。
 * - 如果行数、列数变动范围很大（比如数万），不宜轻率地按最大数量分配。
 *
 * 功能点：
 *
 * - 类模板名为Matrix。
 *    - 有一个模板参数T，默认类型参数为double。
 * - 三个数据成员：
 *    - 行数m_row，整型。
 *    - 列数m_col，整型。
 *    - 指针m_elm，指向T类型的一级指针，后续存取时按行计算偏移量。
 * - 缺省构造函数
 *    - 初始化为0行、0列、元素指针为nullptr。
 * - 自定义构造函数
 *    - 有3个参数：
 *      - 行数row
 *      - 列数col
 *      - 初始值数组（指针）values[]，缺省实参nullptr。
 *    - 使用new T[]分配长为（行数×列数）的元素数组。
 *    - 如果values非空，需逐个复制到元素数组，初始值应不少于矩阵容量。
 * - 拷贝构造函数
 *    - 参数名为that，同类型矩阵的“常性引用”。
 *    - 分配与that等长的元素数组，并逐个复制其元素。
 *    - 自定义构造函数和拷贝构造函数是唯二的资源分配点。
 * - 析构函数
 *    - 声明为普通成员函数（非虚函数），表示该类不期望被继承。
 *    - 析构函数是唯一的资源释放点，使用delete[]释放分配的元素。
 * - 交换函数：`swap(&that)`
 *    - 无返回值。有1个同类引用参数that。
 *    - 若this不等于&that，则交换this和that的三个数据成员。
 *    - 该函数不抛出异常、不分配或释放内存，非常可靠且关键。
 * - 拷贝赋值算符`=(&that)`
 *    - 能够应对自赋值。
 *    - 借助swap()成员函数实现。
 * - 获取行数`row()`
 * - 获取列数`col()`
 *    - 返回整型的行数、列数。应为常性、内联函数。
 * - 更改维度：resize(r, c)
 *    - 用自定义构造函数定义局部对象，用swap()与this交换，不要直接new/delete。
 *    - 如果新容量(r * c)与当前容量相同则仅修改尺寸，不重分配。
 * - 重载下标算符`[]`
 *    - 返回指向第i行元素的指针。
 *    - 有1个形参i，表示行下标。
 * - 重载常性下标算符`[]`
 *    - 返回指向第i行元素的常性指针。
 *    - 有1个形参i，表示行下标。
 *    - 不改变实例内容，定义为常性成员函数。
 * - 重载数乘自反赋值算符`*=`
 *    - 返回实例自身的引用。
 *    - 有1个形参t，类型为T。
 *    - 功能是将当前实例的所有元素均乘以参数t。
 * - 重载加法自反赋值算符`+=`
 *    - 返回实例自身的引用。
 *    - 有1个形参that，同类型常性引用。
 *    - 需要检查this和that的维度，如不同则抛出std::runtime_error异常。数乘无此要求。
 *    - 调用后this各元素加上that的对应元素。
 * - 重载矩阵乘法算符`*`
 *    - 返回矩阵相乘结果Matrix<T>，不应为引用或指针。
 *    - 检查this和that的维度，this的列数应等于that的行数，如不符则抛出std::runtime_error异常。
 *    - 不改变实例本身，定义为常性成员函数。
 *    - 矩阵乘法需要保存中间结果且不改变this，因此先实现`*`而非`*=`。
 * - 用模板函数重载全局算符`ostream& operator<<(ostream& ost, const Matrix<T>& )`
 *    - 输出符用法类似`cout << matrix`，相当于`operator<<(cout, matrix)`，首个参数是输出流而不是矩阵，
 *      因此不能定义为成员函数。输出流属于标准库，也不能修改，所以只能重载全局`<<`算符。
 *    - 逐行输出矩阵元素，每行中元素之间间隔一个空格。精度无需设置，默认即可。
 * 
 * 在前述功能的基础上，实现下列算符（通常两三行即可）：
 * 
 * - 重载数乘算符`*`
 *    - 返回数乘结果Matrix<T>，不应为引用或指针。
 *    - 借助拷贝构造函数和`*=`算符实现。
 *    - 不改变实例本身，定义为常性成员函数。
 * - 重载加法算符`+`
 *    - 返回相加结果Matrix<T>，不应为引用或指针。
 *    - 借助拷贝构造函数和之前定义的`+=`算符实现。
 *    - 不改变实例本身，定义为常性成员函数。
 * - 重载乘法自反赋值算符`*=`
 *    - 借助之前定义的矩阵乘法算符`*`将结果保存在临时变量，再与this交换。
 *    - 可能改变矩阵的维度。
 * 
 * 选做功能：
 * 
 * - 移动构造函数`Matrix(Matrix&& that)`
 *    - 正确声明参数that类型，即同类型矩阵的“右值引用&&”，而非常性引用。
 *    - 将that的内容移动到当前实例，然后that置空（0行、0列、空指针）。
 * - 移动赋值算符`Matrix& operator=(Matrix&& that)`
 *    - 定义局部空矩阵temp。
 *    - this与that交换，this得到实参的内容。
 *    - that与temp交换，that变为空，temp得到原来this的内容。
 *    - temp是局部对象，函数返回时自动析构，释放内存。
 */

#include <cassert>
#include <ostream>
#include <stdexcept>

/// TODO: 实现Matrix<T>

/// TODO: 实现全局的输出符重载，需要写成函数模板。

#include <iostream>
#include <sstream>
using namespace std;

int main() {
  { // 验证模板参数
    static_assert(sizeof(Matrix<float>::value_type) == 4);
    static_assert(sizeof(Matrix<double>::value_type) == 8);
    static_assert(sizeof(Matrix<>::value_type) == 8);

    static_assert(sizeof(Matrix<>) == 4 + 4 + 8); // int, int, double*
  }
  { // 缺省构造
    Matrix<> m;
    assert(m.row() == 0);
    assert(m.col() == 0);
    assert(m[0] == nullptr);
  }
  { // 指定构造
    Matrix<> m0(3, 4);
    assert(m0.row() == 3);
    assert(m0.col() == 4);
    assert(m0[0] != nullptr);
    assert(m0[1] != nullptr);

    const double v[] = {1, 2, 3, 4, 5, 6};
    Matrix<> m1(2, 3, v);
    assert(v[0] == m1[0][0]);
    assert(v[1] == m1[0][1]);
    assert(v[2] == m1[0][2]);
    assert(v[3] == m1[0][3]); // 可以，矩阵存储连续
    assert(v[4] == m1[0][4]);
    assert(v[5] == m1[0][5]);

    // 拷贝构造
    auto m2(m1);
    assert(m2.row() == 2);
    assert(m2.col() == 3);
    assert(v[0] == m2[0][0]);
    assert(v[1] == m2[0][1]);
    assert(v[2] == m2[0][2]);
    assert(v[3] == m2[1][0]);
    assert(v[4] == m2[1][1]);
    assert(v[5] == m2[1][2]);

    // swap
    auto p0 = m0[0];
    auto p2 = m2[0];
    m0.swap(m2);
    assert(m2.row() == 3);
    assert(m2.col() == 4);
    assert(m2[0] == p0);
    assert(m0[0] == p2);

    assert(m0.row() == 2);
    assert(m0.col() == 3);
    assert(v[0] == m0[0][0]);
    assert(v[1] == m0[0][1]);
    assert(v[2] == m0[0][2]);
    assert(v[3] == m0[1][0]);
    assert(v[4] == m0[1][1]);
    assert(v[5] == m0[1][2]);

    m0 *= 2;
    assert(v[0] * 2 == m0[0][0]);
    assert(v[1] * 2 == m0[0][1]);
    assert(v[2] * 2 == m0[0][2]);
    assert(v[3] * 2 == m0[1][0]);
    assert(v[4] * 2 == m0[1][1]);
    assert(v[5] * 2 == m0[1][2]);

    m0 += m1;
    assert(v[0] * 3 == m0[0][0]);
    assert(v[1] * 3 == m0[0][1]);
    assert(v[2] * 3 == m0[0][2]);
    assert(v[3] * 3 == m0[1][0]);
    assert(v[4] * 3 == m0[1][1]);
    assert(v[5] * 3 == m0[1][2]);
  }
  {
    // 测试矩阵乘法
    double va[] = {2, 3};
    double vb[] = {5, 7};
    Matrix<> a(2, 1, va);
    Matrix<> b(1, 2, vb);
    auto c = a * b;
    assert(c.row() == 2);
    assert(c.col() == 2);
    assert(c[0][0] == 10);
    assert(c[0][1] == 14);
    assert(c[1][0] == 15);
    assert(c[1][1] == 21);

    // 测试流输出算符的重载
    ostringstream oss;
    oss << c;
    assert(oss.str() == "10 14\n15 21\n");
  }
}